package com.huoli.trip.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * redis操作工具类
 *
 * @author yinh
 * @date 2024-06-06 09:49
 */
@Component
@Slf4j
public class RedisUtil {

    private final StringRedisTemplate stringRedisTemplate;
    private static final ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);


    public RedisManager(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 获取缓存
     *
     * @param key 键
     * @return 值
     */
    public <T> T get(String key, Class<T> clazz) {
        try {
            String value = stringRedisTemplate.opsForValue().get(key);
            return deserialize(value, clazz);
        } catch (Exception e) {
            log.error("Failed to get value for key: {}", key, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 写入缓存
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public <T> boolean set(String key, T value) {
        try {
            stringRedisTemplate.opsForValue().set(key, serialize(value));
            return true;
        } catch (Exception e) {
            log.error("Failed to set value for key: {}", key, e);
            return false;
        }
    }

    /**
     * 写入缓存并设置过期时间
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间(类型：秒)
     * @return true成功 false失败
     */
    public <T> boolean set(String key, T value, long timeout) {
        return this.set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 写入缓存并设置过期时间
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     * @return true成功 false失败
     */
    public <T> boolean set(String key, T value, long timeout, TimeUnit timeUnit) {
        try {
            stringRedisTemplate.opsForValue().set(key, serialize(value), timeout, timeUnit);
            return true;
        } catch (Exception e) {
            log.error("Failed to set value with expiration for key: {}", key, e);
            return false;
        }
    }

    /**
     * 对键的值进行自增1
     *
     * @param key 键
     * @return 自增后的值
     */
    public Long increment(String key) {
        try {
            return stringRedisTemplate.opsForValue().increment(key);
        } catch (Exception e) {
            log.error("Failed to increment value for key: {}", key, e);
            return null;
        }
    }

    /**
     * 对键的值增加指定的整数
     *
     * @param key   键
     * @param delta 增加的值
     * @return 自增后的值
     */
    public Long incrementBy(String key, long delta) {
        try {
            return stringRedisTemplate.opsForValue().increment(key, delta);
        } catch (Exception e) {
            log.error("Failed to increment value for key: {}", key, e);
            return null;
        }
    }

    /**
     * 设置哈希表中的字段值
     *
     * @param key     哈希表键
     * @param hashKey 哈希表字段键
     * @param value   哈希表字段值
     * @return true成功 false失败
     */
    public <T> boolean setHashValue(String key, String hashKey, T value) {
        try {
            stringRedisTemplate.opsForHash().put(key, hashKey, serialize(value));
            return true;
        } catch (Exception e) {
            log.error("Failed to set hash value for key: {} and hashKey: {}", key, hashKey, e);
            return false;
        }

    }

    /**
     * 设置哈希表中的字段值并设置过期时间
     *
     * @param key      哈希表键
     * @param hashKey  哈希表字段键
     * @param value    哈希表字段值
     * @param timeout  过期时间(单位：秒)
     * @return true成功 false失败
     */
    public <T> boolean setHashValue(String key, String hashKey, T value, long timeout) {
        return this.setHashValue(key, hashKey, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置哈希表中的字段值并设置过期时间
     *
     * @param key      哈希表键
     * @param hashKey  哈希表字段键
     * @param value    哈希表字段值
     * @param timeout  过期时间
     * @param timeUnit 过期时间单位
     * @return true成功 false失败
     */
    public <T> boolean setHashValue(String key, String hashKey, T value, long timeout, TimeUnit timeUnit) {
        try {
            // 将timeout转换为秒数
            long timeoutInSeconds = timeUnit.toSeconds(timeout);

            // 定义Lua脚本
            String luaScript = "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " +
                    "redis.call('expire', KEYS[1], ARGV[3]);";

            // 创建RedisScript对象
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(luaScript);
            redisScript.setResultType(Long.class);

            // 执行Lua脚本
            stringRedisTemplate.execute(redisScript, Collections.singletonList(key), hashKey, serialize(value),
                    String.valueOf(timeoutInSeconds));
            return true;
        } catch (Exception e) {
            log.error("Failed to set hash value with expiration for key: {} and hashKey: {}", key, hashKey, e);
            return false;
        }
    }

    /**
     * 获取哈希表中的字段值
     *
     * @param key     哈希表键
     * @param hashKey 哈希表字段键
     * @param clazz   返回对象的类型
     * @return 哈希表字段值
     */
    public <T> T getHashValue(String key, String hashKey, Class<T> clazz) {
        try {
            String value = (String) stringRedisTemplate.opsForHash().get(key, hashKey);
            return deserialize(value, clazz);
        } catch (Exception e) {
            log.error("Failed to get hash value for key: {} and hashKey: {}", key, hashKey, e);
            return null;
        }
    }

    /**
     * 对哈希表字段值进行自增1
     *
     * @param key     哈希表键
     * @param hashKey 哈希表字段键
     * @return 自增后的值
     */
    public Long incrementHashValue(String key, String hashKey) {
        try {
            return stringRedisTemplate.opsForHash().increment(key, hashKey, 1L);
        } catch (Exception e) {
            log.error("Failed to increment hash value for key: {} and hashKey: {}", key, hashKey, e);
            return null;
        }
    }

    /**
     * 对哈希表字段值增加指定的整数
     *
     * @param key     哈希表键
     * @param hashKey 哈希表字段键
     * @param delta   增加的值
     * @return 自增后的值
     */
    public Long incrementHashValueBy(String key, String hashKey, long delta) {
        try {
            return stringRedisTemplate.opsForHash().increment(key, hashKey, delta);
        } catch (Exception e) {
            log.error("Failed to increment hash value for key: {} and hashKey: {}", key, hashKey, e);
            return null;
        }
    }

    /**
     * 对哈希表字段值进行自增1, 并对表键设置过期时间
     *
     * @param key     哈希表键
     * @param timeout  过期时间(单位：秒)
     * @return 自增后的值
     */
    public Long incrementHashValue(String key, String hashKey, long timeout) {
        return this.incrementHashValue(key, hashKey, timeout, TimeUnit.SECONDS);
    }

    /**
     * 对哈希表字段值进行自增1, 并对表键设置过期时间
     *
     * @param key     哈希表键
     * @param timeout  过期时间
     * @param timeUnit 过期时间单位
     * @return 自增后的值
     */
    public Long incrementHashValue(String key, String hashKey, long timeout, TimeUnit timeUnit) {
        try {
            // 将timeout转换为秒数
            long timeoutInSeconds = timeUnit.toSeconds(timeout);

            // 定义Lua脚本
            String luaScript = "local newVal = redis.call('hincrby', KEYS[1], ARGV[1], 1); " +
                    "redis.call('expire', KEYS[1], ARGV[2]); " +
                    "return newVal;";

            // 创建RedisScript对象
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(luaScript);
            redisScript.setResultType(Long.class);

            // 执行Lua脚本
            return stringRedisTemplate.execute(redisScript, Collections.singletonList(key), hashKey,
                    String.valueOf(timeoutInSeconds));
        } catch (Exception e) {
            log.error("Failed to increment hash value and set expiration for key: {} and hashKey: {}", key, hashKey, e);
            return null;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 键
     * @return true成功 false失败
     */
    public boolean delete(String key) {
        try {
            if (key != null) {
                stringRedisTemplate.delete(key);
            }
            return true;
        } catch (Exception e) {
            log.error("Failed to delete key: {}", key, e);
            return false;
        }
    }

    /**
     * 检查键是否存在
     *
     * @param key 键
     * @return true存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return key != null && Boolean.TRUE.equals(stringRedisTemplate.hasKey(key));
        } catch (Exception e) {
            log.error("Failed to check if key exists: {}", key, e);
            return false;
        }
    }

    /**
     * 设置缓存过期时间
     *
     * @param key     键
     * @param timeout 过期时间
     * @param unit    时间单位
     * @return true成功 false失败
     */
    public boolean expire(String key, long timeout, TimeUnit unit) {
        try {
            return key != null && Boolean.TRUE.equals(stringRedisTemplate.expire(key, timeout, unit));
        } catch (Exception e) {
            log.error("Failed to set expire for key: {}", key, e);
            return false;
        }
    }

    /**
     * 获取缓存过期时间
     *
     * @param key  键
     * @param unit 时间单位
     * @return 过期时间
     */
    public long getExpire(String key, TimeUnit unit) {
        try {
            Long expire = stringRedisTemplate.getExpire(key, unit);
            return expire != null ? expire : 0L;
        } catch (Exception e) {
            log.error("Failed to get expiration for key: {}", key, e);
            return 0L;
        }
    }

    /**
     * 获取锁
     *
     * @param key     锁的键
     * @param value   锁的值
     * @param timeout 锁的过期时间(单位：秒)
     * @return true成功 false失败
     */
    public boolean lock(String key, String value, long timeout) {
        return this.lock(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 获取锁
     *
     * @param key     锁的键
     * @param value   锁的值
     * @param timeout 锁的过期时间
     * @param unit    时间单位
     * @return true成功 false失败
     */
    public boolean lock(String key, String value, long timeout, TimeUnit unit) {
        try {
            Boolean success = stringRedisTemplate.execute((RedisCallback<Boolean>) connection ->
                    ((StringRedisConnection) connection).set(key, value, Expiration.from(timeout, unit),
                            RedisStringCommands.SetOption.ifAbsent()));
            return Boolean.TRUE.equals(success);
        } catch (Exception e) {
            log.error("Failed to acquire lock for key: {}", key, e);
            return false;
        }
    }

    /**
     * 释放锁
     * @param key 锁的键
     * @param value 锁的值
     * @return true成功 false失败
     */
    public boolean unlock(String key, String value) {
        try {
            // Lua脚本保证原子性
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del', KEYS[1]) " +
                    "else return 0 end";
            Long result = stringRedisTemplate.execute(RedisScript.of(luaScript, Long.class),
                    Collections.singletonList(key), value);
            return result != null && result > 0;
        } catch (Exception e) {
            log.error("Failed to release lock for key: {}", key, e);
            return false;
        }
    }

    /**
     * 序列化对象为JSON字符串
     *
     * @param object 要序列化的对象
     * @param <T>    对象的范型
     * @return 序列化后的JSON字符串
     * @throws JsonProcessingException 序列化失败抛出异常
     */
    private <T> String serialize(T object) throws JsonProcessingException {
        return objectMapper.writeValueAsString(object);
    }

    /**
     * 将JSON字符串反序列化为指定类的对象。
     *
     * @param value JSON字符串
     * @param clazz 要反序列化为的类
     * @param <T>   对象的范型
     * @return 反序列化后的对象
     * @throws JsonProcessingException 反序列化失败抛出异常
     */
    private <T> T deserialize(String value, Class<T> clazz) throws JsonProcessingException {
        if (value == null) {
            return null;
        }
        return objectMapper.readValue(value, clazz);
    }
}